简单做个实验分析一下 new/new[] 和 delete/delete[]

先来介绍下流程。

  • new/new[],他的流程是先开辟了一个空间,然后调用了构造函数。
  • delete/delete[]先调用析构函数,然后释放内存。

那么如果new/deletenew[]/deletep[]没有配对会出现什么问题。
对于这个问题我做了简单的实验。

非基础类型

也就是自己写的类。
先说下结论

  • delete释放new[]只会调用第一个的析构函数,导致内存泄漏。
  • delete[]释放new会出bug

测试如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A{
int a;
public:
A():a(0xffff){
cout<<"A()\n";
}
~A(){
cout<<"~A()\n";
}
virtual void func(){
cout<<"func\n";
}
};
int main() {
A *b=new A[4];
delete [] b;

b =new A();
delete b;

b=new A[4];
delete b;

A *a=new A();
delete[] a;
return 0;
}

输出结果是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
A()
A()
A()
A()
~A()
~A()
~A()
~A()//4个析构
A()
~A()//一个析构
A()
A()
A()
A()
~A()//一个析构
A()

Process finished with exit code -1073741819 (0xC0000005)//直接RE了

问题已经看出来,那么是为什呢。
我们去看看内存。
在这里插入图片描述
看到了吗,十分明显的一个数字,表示分配了多少个,后面很明显吧。一个虚函数指针,和一个值。
然后执行下一行释放内存。
在这里插入图片描述
看出内存的变化了吗,全部都释放掉了,前面那个4 也没了。
在这里插入图片描述
执行到这,用new创建的明显没有一个表示多少个的。
执行下一行
在这里插入图片描述
然后没了被释放了。

执行后面错误的。
看看用delele删除new[]
在这里插入图片描述

在这里插入图片描述
执行完后,他们全部都在,我都怀疑内存到底有没有释放。很显然全部都还没有释放掉。

然后后面那个为啥会RE不用我说了吧。
在这里插入图片描述
前面那个表述个数的数字这么大。。你想怎么搞。。。

小问题1不要慌

对于这个实验,如果把虚函数删掉,就不会是内存错误而是一个非常大的循环。至于为什么自己思考。

结论:

new参数的非基础类型,只会开辟一个对象内存空间,new[]还会保存一个数组大小。delete[]删除的时候会根据 删除地址前面的4个字节表示的数字大小来删除后面的对象。

为了保证正确性,我来做个有意思的事,我们来手写一个new[]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A{
int a;
public:
A():a(0xffff){
cout<<"A()\n";
}
~A(){
cout<<"~A()\n";
}
virtual void func(){
cout<<"func\n";
}
};
int main() {
void *p=::operator new(sizeof(A)*4+sizeof(int));//这个可以用malloc来是一样的结果。
int *size=(int *)p;
*size=4;//这个设置一下大小
A *a=(A *)(size+1);//这个就是new[] 的实现
new(a)A();
new(a+1)A();
new(a+2)A();
new(a+3)A();
delete[] a;//显然这个是释放
return 0;
}

理论上delete []也能手写实现,具体怎么实现读者自己动手操作,切忌拿来主义。

基本类型

对于基本类型和非基本类型有什么区别呢???,emmmm好像没啥区别。
但是他确实不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
int *b=new int[4]{0xfff,0xffff,0xfff,0xfff};
delete [] b;

b =new int();
delete b;

b=new int[4]{0xfff,0xffff,0xfff,0xfff};
delete b;

int *a=new int();
delete[] a;
return 0;
}

运行这个你会发现,没有任何问题!!!!,确实就是,没有任何问题。
为什么会这样???。
我们照着前面那个的来。
在这里插入图片描述
看到了吗,没有那个表示数组长度的东西了。
在这里插入图片描述
delete之后内存变了。

后面的自己看,你会发现,用delet删除new[]完全没问题。。。
delet[]删除new,好像也没啥问题。

那么问题来了,他们是怎么知道要释放多少内存的。
这个就比较复杂了。他和free是一个性质,内存分配是交给操作系统完成的,具体怎么操作的还是和内核有关。
应该也能很清楚的看见,每次释放内存,改变的不仅仅是你需要的的内存大小,而是一整块内存。
所以用delete/delete[]释放内存的时候,实际上是直接把那一块内存释放了。

结论:

对于基础类型,用delete/delete[]删除实际上是一样的,主要原因应该还是因为,他没有调用构造和析构的必要。如果一个类里面封装了一个指针,然后new了一个对象,如果不调用析构函数,就会出现内存泄漏。